import random
import time
from datetime import datetime
from threading import Thread, Lock


class Participant:
    def __init__(self, participant_id, tools_request_list, tools_participant_list, tools_lock):
        self.request_list = tools_request_list  # 绑定共享的请求列表
        self.participant_list = tools_participant_list  # 绑定到共享的参与者列表
        self.responded_list = []  # 已响应列表，记录自己已响应过的请求，防止重复响应
        self.participant_id = participant_id  # 参与者id,主线程创建该参与者时提供
        self.request_time = datetime.now()  # 请求时间
        self.response_time = datetime.now()  # 处理响应时间
        self.get_response_num = -1  # 请求同意的个数
        self.is_using = False  # 是否正在使用临界资源，默认为False
        self.lock = tools_lock  # 绑定同一个资源互斥锁

    # TODO 1:
    #   思路：每个参与者都有一个共享队列存放公共的请求，如果有请求就放进去，然后按时间排序(按道理放的时候
    #   是按时间顺序的，可以不排序，但是可能有偶然情况，比自己请求晚的先放进去了，还是排序比较稳妥）
    #   然后设置一个标志量记录自己是否正在使用临界资源，如果没有，就同意请求队列每个对象的请求，
    #   但问题是，如果是无限循环，怎么做到不重复同意同一个请求？
    #   如果自己正在使用，就不予理会，当某个参与者对象获取到除自己外所有参与者的请求后，就可以去使用临界资源了，
    #   其他参与者同意时直接对请求的参与者对象修改其同意数+1，然后请求者需要访问临界资源时，一直检测
    #   自己的同意数是否达到n-1，达到就收到全部同意，访问临界资源，然后继续工作，同时随时可以下一次请求。
    #   否则就一直处于阻塞死循环，直到检测到自己集齐所有同意。然后访问完临界资源就重置自己有关请求的属性
    #   值，继续工作，随时可以发起下一次请求。
    # TODO 2：对于上述重复同意问题的解决
    #  解决重复同意同一请求问题的思路：每个参与者自带一个已回复的列表，记录已经回复过的对象队列
    #  如果每次取当前公共请求队列与自身已回复队列的差集进行回复，即做到了只回复未回复的，然后当前请求队列
    #  就全都是已回复的了，更新自己的已回复队列为当前公共的请求队列。
    # TODO 3:
    #  开始没有给访问临界资源上锁，导致可能出现几个参与者同时集齐所有同意，同时进入临界资源，算是个逻辑bug
    #  最后在工具类设置了一个互斥锁，所有参与者共享这个锁，并且在进入临界资源时需要使用这个锁，就保证了
    #  所有参与者访问临界资源成为原子性操作
    def start(self):
        while 1:
            if self.get_response_num == -1:  # 如果请求响应个数为-1，说明还未发起请求，则可以发起下一次请求
                self.get_response_num = 0  # 将得到响应的个数初始化为0
                self.request_time = datetime.now()
                self.send_request()
            # 是否集齐n-1个同意 并且自己的请求当前排在首位
            if self.get_response_num == len(self.participant_list) - 1 and \
                    self.request_list[0] == f'{self.request_time}_{self.participant_id}':
                with self.lock:  # 保证访问临界资源时为原子操作，相当于给这个操作加锁
                    self.working()
            self.agree()  # 每次循环都调用一次agree,判断是否有新请求可以被同意
            time.sleep(random.randint(1, 4))  # 随机时长的等待

    def send_request(self):
        self.request_list.append(f'{self.request_time}_{self.participant_id}')
        self.request_list.sort()
        print(f'参与者{self.participant_id}:已于{self.request_time} 发送请求，正在等待响应！')

    def working(self):
        self.is_using = True  # 标记为正在使用临界资源
        print(f'参与者{self.participant_id}:请求已全体通过，开始访问临界资源！')
        # TODO：此处可以为访问临界资源的相关操作,此处用睡眠1秒代替
        time.sleep(1)
        self.get_response_num = -1  # 访问完成，请求响应数置为-1，即未请求状态
        self.is_using = False  # 标记自己为未使用状态
        print(f'参与者{self.participant_id}:访问临界资源完成，可以继续工作了！')
        # 删除自己的请求，此处使用del的目的是在删除元素的同时释放其空间，节约内存，
        # 如果仅想达到删除元素的目的，也可以简单的使用列表自带的remove方法
        self.delete_request()
        return

    def delete_request(self):
        # 删除自己的请求，此处使用del的目的是在删除元素的同时释放其空间，节约内存，
        # 如果仅想达到删除元素的目的，也可以简单的使用列表自带的remove方法
        req_index = self.request_list.index(f'{self.request_time}_{self.participant_id}')  # 拿到请求值的索引
        del self.request_list[req_index]  # 根据索引del(销毁)列表中该请求的元素对象

    def agree(self):
        # 同意请求的相关操作
        if self.is_using:  # 自己正在使用
            return
        request_list = self.request_list.copy()  # 使用浅拷贝，复制一份副本，否则会直接和这个复制对象绑定，同步更新
        response_obj_list = list(set(request_list) - set(self.responded_list))
        response_obj_list.sort()
        for response_obj in response_obj_list:
            response = response_obj.split('_')[1]
            response = int(response)
            if response != self.participant_id:
                self.participant_list[response].get_response_num += 1
                self.response_time = datetime.now()
                print(f'参与者{self.participant_id}：来自参与者{response}的请求处理完成！处理时间：{self.response_time}')
        self.responded_list = request_list  # 更新已响应列表


class Tools:  # 工具类，只起到一个提供共享数据的途径，实际应用时可以替换为其他通信方式以共享数据
    def __init__(self):
        self.participant_list = []  # 参与者列表
        self.request_list = []  # 请求列表
        self.lock = Lock()  # 临界资源访问的锁


def create_thread(num):
    global participant_thread_list

    # 创建工具类
    tools = Tools()

    # 创建参与者线程
    for i in range(0, num):  # TODO：可扩展为线程池的方式
        participant = Participant(i, tools.request_list, tools.participant_list, tools.lock)
        tools.participant_list.append(participant)  # 添加到协调者拥有的参与者列表
        participant_i = Thread(target=participant.start, name=f'participant_{i}')
        participant_thread_list.append(participant_i)
        participant_i.setDaemon(True)  # 守护线程
        participant_i.start()  # 参与者线程启动

    for i in range(0, num):
        participant_thread_list[i].join()


def main(num):
    create_thread(num)  # 创建参与者和协调者，开始工作


if __name__ == '__main__':
    participant_thread_list = []  # 参与者线程列表
    participant_num = int(input("请输入参与者个数："))
    main(participant_num)
